<!DOCTYPE html>
<html class="no-js" lang="zh">
  <head>
<meta charset="utf-8">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<link rel="alternate" href="https://www.desgard.com" hreflang="pt-BR">
<link href="http://gmpg.org/xfn/11" rel="profile">
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<meta name="theme-color" content="#005344">
<title>Guardia · 瓜地</title>
<meta name="google-site-verification" content="zS1dSn20XtA4FJYEOQLXqI0boxZdMnJ2g3beje-cl20">
<meta name="description" content="I write many code to write less code.💻">
<meta name="keywords" content="">
<!-- Social: Facebook / Open Graph -->
<meta property="og:url" content="https://www.desgard.com/SDWebImage2/">
<meta property="og:title" content="       SDWebImage Source Probe: Manager | Gua  ">
<meta property="og:description" content="I write many code to write less code.💻">
<meta property="og:site_name" content="Desgard_Duan">
<meta property="og:locale" content="pt_BR">
<meta property="og:type" content="website">
<meta property="og:author" content="https://www.facebook.com/desgard.duan">
<meta property="og:image" content="https://www.desgard.com">
<!-- Social: Twitter -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@nandomoreirame">
<meta name="twitter:domain" content="https://www.desgard.com">
<meta name="twitter:title" content="       SDWebImage Source Probe: Manager | Gua  ">
<meta name="twitter:description" content="I write many code to write less code.💻">
<meta name="twitter:image:src" content="https://www.desgard.com">
<!-- Favicons -->
<link rel="apple-touch-icon" sizes="114x114" href="https://www.desgard.com/assets/ico/apple-touch-icon-114-516f4e19976b9e4dbb77ad9b576831fe.png">
<link rel="apple-touch-icon" sizes="72x72" href="https://www.desgard.com/assets/ico/apple-touch-icon-72-5409b2df229305703caf583d86c845ab.png">
<link rel="apple-touch-icon" href="https://www.desgard.com/assets/ico/apple-touch-icon-57-aa873e019cf659e0d4e6a0b5bb9f379d.png">
<link rel="shortcut icon" href="https://www.desgard.com/assets/ico/favicon-4298be3d3fbe23e18d65bded9d930899.png">
<!-- rel prev and next -->
<!-- Canonical link tag -->
<link rel="canonical" href="https://www.desgard.com/SDWebImage2/">
<link rel="alternate" type="application/rss+xml" title="Gua" href="https://www.desgard.com/feed.xml">
<link rel="stylesheet" href="/assets/main-0b7b828712f4c43b75bba4861c907bb1.css">
<script src="/assets/modernizr-custom-cb807611a3e262b2eac59444cbab74d6.js" data-cfasync="false"></script>
<script src="/assets/js/jquery-3.1.js"></script>
<script src="/assets/js/jquery.rotate.js"></script>
<script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<script type="application/ld+json">
{
  "@context": "http://schema.org",
  "@type": "Website",
  "publisher": "www.desgard.com",
  "url": "http://www.desgard.com/",
  "description": "瓜地"
}
</script>
<script type="text/javascript">
  var disqus_shortname = 'desgard',
      baseurl          = '';
</script>
<!--
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-52446115-1']);
_gaq.push(['_trackPageview']);
(function() {
  var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
  ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
  var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>-->
<script>
var _hmt = _hmt || [];
(function() {
  var hm = document.createElement("script");
  hm.src = "//hm.baidu.com/hm.js?c5a8123bc51782a3083a631ed9ff50e4";
  var s = document.getElementsByTagName("script")[0]; 
  s.parentNode.insertBefore(hm, s);
})();
</script>
<script type="text/x-mathjax-config">
    MathJax.Hub.Config({
      tex2jax: {
        inlineMath: [ ['$','$'] ],
        displayMath: [ ['$$','$$'] ],
        processEscapes: true
      }
    });
  </script>
  </head>
  <body class="post-template single">
    <header class="header">
  <div class="container">
    <h1><a href="/"><strong>desgard</strong>.com</a></h1>
    <nav class="navbar">
      <ul>
        <li><a href="/">Home</a></li>
        <li><a href="#!" data-modal="modalSearch"><i class="fa fa-search"></i></a></li>
        <li><a href="/comment " target="_blank"><i class="fa fa-comments"></i></a></li>
        <li><a href="/feed.xml" target="_blank"><i class="fa fa-feed"></i></a></li>
      </ul>
    </nav>
  </div>
</header>
    <main class="wrapper container" itemprop="mainContentOfPage" itemscope="itemscope" itemtype="http://schema.org/Blog">
      <article class="post" itemscope="itemscope" itemtype="http://schema.org/BlogPosting" itemprop="blogPost">
  <header class="post-header">
    <h1>SDWebImage Source Probe: Manager</h1>
    <div class="post-meta">
      <time datetime="2015-10-13">
        <i class="fa fa-calendar-o"></i> <time datetime="2016-09-15"> 2016-09-15
      </time>
      <span>
        <i class="fa fa-comments"></i> <a href="#container">Comment me!</a>
      </span>
      <span>
<!--
        <i class="fa fa-folder-open-o"></i> 
-->
      </span>
    </div>
  </header>
  <p>在 <em><a href="">SDWebImage Source Probe: WebCache</a></em> 一文中，通过最常用的 <code>sd_setImageWithURL</code> 方法，来分析源码。而在其中，对于图片的 download 方法，也是需要理解的重点之一。它用于处理异步下载和图片缓存的类，当然也可直接拿来使用。<code>SDWebImageManager</code> 这个类，为 <code>WebCache</code> 、 <code>SDWebImageDownloader</code> 和 <code>SDImageCache</code> 搭建了一个桥梁，使得拥有更好的协同性。而每个类负责的功能不同，又是通过该类进行了结构上的解耦。</p>
<p>这篇通过分析 <code>SDWebImageManager</code> 的 Source Code ，来深入分析一下 SD 三方库中，对于 download 方法具体实现细节。</p>
<h2 id="section">缓存策略一览</h2>
<p>在 WebCache 的 sd_setImageWithURL 方法中的缓存策略，调用了这个方法：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">-</span> <span class="p">(</span><span class="nb">id</span> <span class="o">&lt;</span><span class="no">SDWebImageOperation</span><span class="o">&gt;</span><span class="p">)</span><span class="ss">downloadImageWithURL</span><span class="p">:(</span><span class="no">NSURL</span> <span class="o">*</span><span class="p">)</span><span class="n">url</span>
                                         <span class="ss">options</span><span class="p">:(</span><span class="no">SDWebImageOptions</span><span class="p">)</span><span class="n">options</span>
                                        <span class="ss">progress</span><span class="p">:(</span><span class="no">SDWebImageDownloaderProgressBlock</span><span class="p">)</span><span class="n">progressBlock</span>
                                       <span class="ss">completed</span><span class="p">:(</span><span class="no">SDWebImageCompletionWithFinishedBlock</span><span class="p">)</span><span class="n">completedBlock</span><span class="p">;</span></code></pre></div>
<p>参数解释：</p>
<ul>
  <li><strong>url</strong>：image 对应的 url</li>
  <li><strong>options</strong>：缓存策略枚举</li>
  <li><strong>progress</strong>：在 download 过程中的动作，block 实现</li>
  <li><strong>completed</strong>：在 download 完成后的动作，block 实现</li>
</ul>
<p>在查看方法之前，先来查看一下缓存策略枚举（options）是如何定义的，在源码中作者已经在注释里描述了每一种枚举代表的含义，这里笔者翻译了一下：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">typedef</span> <span class="no">NS_OPTIONS</span><span class="p">(</span><span class="no">NSUInteger</span><span class="p">,</span> <span class="no">SDWebImageOptions</span><span class="p">)</span> <span class="p">{</span>
    <span class="sr">//</span> <span class="err">下载失败后会继续尝试下载</span>
    <span class="no">SDWebImageRetryFailed</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">0</span><span class="p">,</span>
    <span class="sr">//</span> <span class="err">当正在进行</span> <span class="no">UI</span> <span class="err">交互时，自动暂停内部的一些下载操作</span>
    <span class="sr">//</span> <span class="err">一种延迟下载策略。比如在</span> <span class="no">UIScrollView</span> <span class="err">快速滑动的时候暂停下载</span>
    <span class="sr">//</span> <span class="err">但是当滑动速度减慢时，下载开始</span>
    <span class="no">SDWebImageLowPriority</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">1</span><span class="p">,</span>
    <span class="sr">//</span> <span class="err">只进行内存缓存，不缓存到硬盘</span>
    <span class="no">SDWebImageCacheMemoryOnly</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">2</span><span class="p">,</span>
    <span class="sr">//</span> <span class="err">渐进式下载模式</span>
    <span class="sr">//</span> <span class="err">图像会像</span> <span class="n">brower</span> <span class="err">一样，一点点加载出来</span>
    <span class="no">SDWebImageProgressiveDownload</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">3</span><span class="p">,</span>
    <span class="sr">//</span> <span class="err">刷新缓存</span>
    <span class="sr">//</span> <span class="err">将硬盘缓存交给硬盘自带的</span> <span class="no">NSURLCache</span> <span class="err">处理</span>
    <span class="sr">//</span> <span class="err">当同一个</span> <span class="no">URL</span> <span class="err">对应的图片频繁改变的时候，可以使用该策略</span>
    <span class="no">SDWebImageRefreshCached</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">4</span><span class="p">,</span>
    <span class="sr">//</span> <span class="err">后台下载，试用与</span> <span class="n">iOS</span> <span class="mi">4</span><span class="o">+</span> <span class="err">系统中</span>
    <span class="no">SDWebImageContinueInBackground</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">5</span><span class="p">,</span>
    <span class="sr">//</span> <span class="err">设置</span> <span class="no">NSMutableURLRequest</span><span class="o">.</span><span class="n">HTTPShouldHandleCookies</span> <span class="o">=</span> <span class="no">YES</span><span class="p">;</span>
    <span class="sr">//</span> <span class="err">从而来处理存储在</span> <span class="no">NSHTTPCookieStore</span> <span class="err">的</span> <span class="n">cookie</span>
    <span class="no">SDWebImageHandleCookies</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">6</span><span class="p">,</span>
    <span class="sr">//</span> <span class="err">允许无效的</span> <span class="no">SSL</span> <span class="err">检验</span>
    <span class="no">SDWebImageAllowInvalidSSLCertificates</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">7</span><span class="p">,</span>
    <span class="sr">//</span> <span class="err">使用高级别的线程权限，默认是等待当前线程完成再进行</span>
    <span class="no">SDWebImageHighPriority</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">8</span><span class="p">,</span>
    <span class="sr">//</span> <span class="err">默认情况向，当网络图片加载时占位图片（</span><span class="n">place</span> <span class="n">holder</span><span class="err">）显示。</span>
    <span class="sr">//</span> <span class="err">若采用此策略，则不会显示占位图片，直到网络图片加载完成后，如果失败则使用占位图片</span>
    <span class="no">SDWebImageDelayPlaceholder</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">9</span><span class="p">,</span>
    <span class="sr">//</span> <span class="err">是否</span> <span class="n">transform</span> <span class="err">图片</span>
    <span class="sr">//</span> <span class="err">常作为</span> <span class="n">transformdownloadedimage</span> <span class="err">代理方法的替代</span>
    <span class="sr">//</span> <span class="err">防止对图片解析时的破坏</span>
    <span class="no">SDWebImageTransformAnimatedImage</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">10</span><span class="p">,</span>
    <span class="sr">//</span> <span class="err">下载完成后，手动设置图片</span>
    <span class="sr">//</span> <span class="err">一般默认情况下，下载完成会自动加载到</span> <span class="no">ImageView</span> <span class="err">上</span>
    <span class="no">SDWebImageAvoidAutoSetImage</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">11</span>
<span class="p">};</span></code></pre></div>
<h2 id="manage-download-image-">Manage Download Image 主要流程</h2>
<p>了解了所有的下载策略，开始阅读实现的源码。</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">if</span> <span class="p">(</span><span class="o">[</span><span class="n">url</span> <span class="ss">isKindOfClass</span><span class="p">:</span><span class="no">NSString</span><span class="o">.</span><span class="n">class</span><span class="o">]</span><span class="p">)</span> <span class="p">{</span>
   <span class="n">url</span> <span class="o">=</span> <span class="o">[</span><span class="no">NSURL</span> <span class="ss">URLWithString</span><span class="p">:(</span><span class="no">NSString</span> <span class="o">*</span><span class="p">)</span><span class="n">url</span><span class="o">]</span><span class="p">;</span>
<span class="p">}</span></code></pre></div>
<p>首先，先考虑到了传递参数 url 类型为 NSString 的情况，在注释中，作者这样写道：</p>
<blockquote>
  <p>没有传递 NSURL ，而是使用 NSString 对象传递 url 是一个很常见的错误。由于一些奇怪的原因，Xcode 并不会抛出类型不匹配的警告。所以，我们于此允许传递 NSString 对象，并自动转换成 NSURL 从而保护该错误。</p>
</blockquote>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">if</span> <span class="p">(</span><span class="o">![</span><span class="n">url</span> <span class="ss">isKindOfClass</span><span class="p">:</span><span class="no">NSURL</span><span class="o">.</span><span class="n">class</span><span class="o">]</span><span class="p">)</span> <span class="p">{</span>
   <span class="n">url</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">;</span>
<span class="p">}</span></code></pre></div>
<p>仍是为了防止参数类型错误，对 url 的类型再次进行了判断。如果非法，则赋 nil 方便后面的排查。</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">__block</span> <span class="no">SDWebImageCombinedOperation</span> <span class="o">*</span><span class="n">operation</span> <span class="o">=</span> <span class="o">[</span><span class="no">SDWebImageCombinedOperation</span> <span class="kp">new</span><span class="o">]</span><span class="p">;</span>
<span class="n">__weak</span> <span class="no">SDWebImageCombinedOperation</span> <span class="o">*</span><span class="n">weakOperation</span> <span class="o">=</span> <span class="n">operation</span><span class="p">;</span></code></pre></div>
<p>当 url 合法性过滤过程完成后，发现了源码中会实例化 <code>SDWebImageCombinedOperation</code> 这么一个对象。这是继承与 <code>NSObject</code> 并遵循 <code>SDWebImageOperation</code> 协议的一个类。</p>
<h3 id="sdwebimagecombinedoperation">SDWebImageCombinedOperation</h3>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="sr">//</span> <span class="no">SDWebImageOperation</span><span class="o">.</span><span class="n">h</span>
<span class="sr">//</span> <span class="err">协议声明</span>
<span class="vi">@protocol</span> <span class="no">SDWebImageOperation</span> <span class="o">&lt;</span><span class="no">NSObject</span><span class="o">&gt;</span>
<span class="o">-</span> <span class="p">(</span><span class="n">void</span><span class="p">)</span><span class="n">cancel</span><span class="p">;</span>
<span class="vi">@end</span></code></pre></div>
<p>这个协议十分简单，仅仅声明了一个 <code>cancel</code> 方法。这里会自然而然的想起 <code>NSOperation</code> 。<code>NSOperation</code> 在处理事件中，会提供一个 cancel 方法可以取消当前的操作。其实，调用这个 cancel 方法，会将 <code>SDWebImageCombinedOperation</code> 持有的 <code>cacheOperation</code> （NSOperation） 和 cancelBlock （block） 给 cancel 掉。代码如下：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="sr">//</span> <span class="no">SDWebImageCombinedOperation</span>
<span class="sr">//</span> <span class="n">cancel</span> <span class="c1">#1</span>
<span class="o">-</span> <span class="p">(</span><span class="n">void</span><span class="p">)</span><span class="n">cancel</span> <span class="p">{</span>
    <span class="nb">self</span><span class="o">.</span><span class="n">cancelled</span> <span class="o">=</span> <span class="no">YES</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">cacheOperation</span><span class="p">)</span> <span class="p">{</span>
        <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">cacheOperation</span> <span class="n">cancel</span><span class="o">]</span><span class="p">;</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">cacheOperation</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">cancelBlock</span><span class="p">)</span> <span class="p">{</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">cancelBlock</span><span class="p">();</span>
        <span class="n">_cancelBlock</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></div>
<p>这个也能体现出 SD 库的简洁接口性。大概了解了代理方法 cancel 在这里的实现，下面来看一下 SDWebImageCombinedOperation 的声明。</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="vi">@interface</span> <span class="no">SDWebImageCombinedOperation</span> <span class="p">:</span> <span class="no">NSObject</span> <span class="o">&lt;</span><span class="no">SDWebImageOperation</span><span class="o">&gt;</span>
<span class="vi">@property</span> <span class="p">(</span><span class="n">assign</span><span class="p">,</span> <span class="n">nonatomic</span><span class="p">,</span> <span class="n">getter</span> <span class="o">=</span> <span class="n">isCancelled</span><span class="p">)</span> <span class="no">BOOL</span> <span class="n">cancelled</span><span class="p">;</span>
<span class="vi">@property</span> <span class="p">(</span><span class="n">copy</span><span class="p">,</span> <span class="n">nonatomic</span><span class="p">)</span> <span class="no">SDWebImageNoParamsBlock</span> <span class="n">cancelBlock</span><span class="p">;</span>
<span class="vi">@property</span> <span class="p">(</span><span class="n">strong</span><span class="p">,</span> <span class="n">nonatomic</span><span class="p">)</span> <span class="no">NSOperation</span> <span class="o">*</span><span class="n">cacheOperation</span><span class="p">;</span>
<span class="vi">@end</span></code></pre></div>
<p>在了解过 SDWebImageCombinedOperation 返回我们的 download 方法中，继续阅读代码。</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="sr">//</span> <span class="err">初始化标记，判断是否为下载失败的</span> <span class="n">url</span>
<span class="no">BOOL</span> <span class="n">isFailedUrl</span> <span class="o">=</span> <span class="no">NO</span><span class="p">;</span>
<span class="sr">//</span> <span class="err">对</span> <span class="n">failedURLs</span> <span class="err">的</span> <span class="n">getter</span> <span class="err">方法加互斥锁</span>
<span class="vi">@synchronized</span> <span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">failedURLs</span><span class="p">)</span> <span class="p">{</span>
	<span class="sr">//</span> <span class="err">更新标记状态</span>
   <span class="n">isFailedUrl</span> <span class="o">=</span> <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">failedURLs</span> <span class="ss">containsObject</span><span class="p">:</span><span class="n">url</span><span class="o">]</span><span class="p">;</span>
<span class="p">}</span>
<span class="sr">//</span> <span class="err">判断</span> <span class="n">url</span> <span class="err">是否为空串；是否下载策略为重新尝试下载且</span> <span class="n">url</span> <span class="err">为失效</span> <span class="n">url</span><span class="err">。</span>
<span class="sr">//</span> <span class="err">两者满足其一进入处理</span>
<span class="k">if</span> <span class="p">(</span><span class="n">url</span><span class="o">.</span><span class="n">absoluteString</span><span class="o">.</span><span class="n">length</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">||</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">options</span> <span class="o">&amp;</span> <span class="no">SDWebImageRetryFailed</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">isFailedUrl</span><span class="p">))</span> <span class="p">{</span>
	<span class="sr">//</span> <span class="err">在</span> <span class="no">SDWebImageCompat</span><span class="o">.</span><span class="n">h</span> <span class="err">中定义的宏</span>
	<span class="sr">//</span> <span class="err">为了安全的跳至主线程上操作</span>
   <span class="n">dispatch_main_sync_safe</span><span class="p">(</span><span class="o">^</span><span class="p">{</span>
   	   <span class="sr">//</span> <span class="err">创建对应问题的</span> <span class="n">error</span> <span class="err">对象</span>
       <span class="no">NSError</span> <span class="o">*</span><span class="n">error</span> <span class="o">=</span> <span class="o">[</span><span class="no">NSError</span> <span class="ss">errorWithDomain</span><span class="p">:</span><span class="no">NSURLErrorDomain</span> <span class="ss">code</span><span class="p">:</span><span class="no">NSURLErrorFileDoesNotExist</span> <span class="ss">userInfo</span><span class="p">:</span><span class="kp">nil</span><span class="o">]</span><span class="p">;</span>
       <span class="sr">//</span> <span class="err">向</span> <span class="n">completionBlock</span> <span class="err">中传参，结束</span> <span class="n">download</span> <span class="err">方法</span> 
       <span class="n">completedBlock</span><span class="p">(</span><span class="kp">nil</span><span class="p">,</span> <span class="n">error</span><span class="p">,</span> <span class="no">SDImageCacheTypeNone</span><span class="p">,</span> <span class="no">YES</span><span class="p">,</span> <span class="n">url</span><span class="p">);</span>
   <span class="p">});</span>
   <span class="k">return</span> <span class="n">operation</span><span class="p">;</span>
<span class="p">}</span></code></pre></div>
<p>注意到两个问题，第一个是互斥锁 <code>@synchronized</code> 。我们知道，在 Foundation 框架中的 Mutable 可变基础类，都是非线程安全的。这里为了避免多线程对于 failedURLs 的篡改，所以对 failedURLs 的 getter 方法执行加锁操作。</p>
<h2 id="dispatchmainsyncsafe">dispatch_main_sync_safe</h2>
<p>对于宏 <code>dispatch_main_sync_safe</code> ，需要读一下它的实现。</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1">#define dispatch_main_sync_safe(block)\</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">[</span><span class="no">NSThread</span> <span class="n">isMainThread</span><span class="o">]</span><span class="p">)</span> <span class="p">{\</span>
        <span class="n">block</span><span class="p">();\</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{\</span>
        <span class="n">dispatch_sync</span><span class="p">(</span><span class="n">dispatch_get_main_queue</span><span class="p">(),</span> <span class="n">block</span><span class="p">);\</span>
    <span class="p">}</span></code></pre></div>
<p>很简单，其实就是将传入的 block 同步加载到主线程中。对应的，在这个宏下面还有一个 “安全异步 block 宏”，其道理和上述宏是一样的：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1">#define dispatch_main_async_safe(block)\</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">[</span><span class="no">NSThread</span> <span class="n">isMainThread</span><span class="o">]</span><span class="p">)</span> <span class="p">{\</span>
        <span class="n">block</span><span class="p">();\</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{\</span>
        <span class="n">dispatch_async</span><span class="p">(</span><span class="n">dispatch_get_main_queue</span><span class="p">(),</span> <span class="n">block</span><span class="p">);\</span>
    <span class="p">}</span></code></pre></div>
<p>下面来看最长的一部分：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="vi">@synchronized</span> <span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">runningOperations</span><span class="p">)</span> <span class="p">{</span>
   <span class="sr">//</span> <span class="err">将</span> <span class="n">operation</span> <span class="err">加入到处理列表中</span>
   <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">runningOperations</span> <span class="ss">addObject</span><span class="p">:</span><span class="n">operation</span><span class="o">]</span><span class="p">;</span>
<span class="p">}</span>
<span class="sr">//</span> <span class="err">获取缓存</span> <span class="n">key</span>
<span class="no">NSString</span> <span class="o">*</span><span class="n">key</span> <span class="o">=</span> <span class="o">[</span><span class="nb">self</span> <span class="ss">cacheKeyForURL</span><span class="p">:</span><span class="n">url</span><span class="o">]</span><span class="p">;</span>
<span class="sr">//</span> <span class="err">在缓存中通过</span> <span class="n">key</span> <span class="err">进行查找，结束后回调</span> <span class="n">done</span> <span class="n">block</span>
<span class="sr">//</span> <span class="err">该方法在本系列下一篇文中会详细介绍</span>
<span class="n">operation</span><span class="o">.</span><span class="n">cacheOperation</span> <span class="o">=</span> <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">imageCache</span> <span class="ss">queryDiskCacheForKey</span><span class="p">:</span><span class="n">key</span> <span class="ss">done</span><span class="p">:</span><span class="o">^</span><span class="p">(</span><span class="no">UIImage</span> <span class="o">*</span><span class="n">image</span><span class="p">,</span> <span class="no">SDImageCacheType</span> <span class="n">cacheType</span><span class="p">)</span> <span class="p">{</span>
   <span class="sr">//</span> <span class="err">判断该</span> <span class="n">operation</span> <span class="err">是否已经被</span> <span class="n">cancel</span>
   <span class="k">if</span> <span class="p">(</span><span class="n">operation</span><span class="o">.</span><span class="n">isCancelled</span><span class="p">)</span> <span class="p">{</span>
       <span class="sr">//</span> <span class="err">如果为真，从处理列表中删除</span> <span class="n">operation</span>
       <span class="vi">@synchronized</span> <span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">runningOperations</span><span class="p">)</span> <span class="p">{</span>
           <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">runningOperations</span> <span class="ss">removeObject</span><span class="p">:</span><span class="n">operation</span><span class="o">]</span><span class="p">;</span>
       <span class="p">}</span>
       <span class="k">return</span><span class="p">;</span>
   <span class="p">}</span>
   <span class="sr">//</span> <span class="err">当</span> <span class="n">callback</span> <span class="err">参数中的</span> <span class="n">image</span> <span class="err">无效</span>
   <span class="k">if</span> <span class="p">((</span><span class="o">!</span><span class="n">image</span> <span class="o">||</span> <span class="n">options</span> <span class="o">&amp;</span> <span class="no">SDWebImageRefreshCached</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="o">![</span><span class="nb">self</span><span class="o">.</span><span class="n">delegate</span> <span class="ss">respondsToSelector</span><span class="p">:</span><span class="vi">@selector</span><span class="p">(</span><span class="ss">imageManager</span><span class="p">:</span><span class="ss">shouldDownloadImageForURL</span><span class="p">:)</span><span class="o">]</span> <span class="o">||</span> <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">delegate</span> <span class="ss">imageManager</span><span class="p">:</span><span class="nb">self</span> <span class="ss">shouldDownloadImageForURL</span><span class="p">:</span><span class="n">url</span><span class="o">]</span><span class="p">))</span> <span class="p">{</span>
       <span class="k">if</span> <span class="p">(</span><span class="n">image</span> <span class="o">&amp;&amp;</span> <span class="n">options</span> <span class="o">&amp;</span> <span class="no">SDWebImageRefreshCached</span><span class="p">)</span> <span class="p">{</span>
           <span class="n">dispatch_main_sync_safe</span><span class="p">(</span><span class="o">^</span><span class="p">{</span>
               <span class="sr">//</span> <span class="err">在</span> <span class="no">SDWebImageRefreshCached</span> <span class="err">策略下，即使在缓存中获取到图片，则需要通知缓存</span>
               <span class="sr">//</span> <span class="err">并重新尝试下载，使得</span> <span class="no">NSURLCache</span> <span class="err">刷新</span>
               <span class="n">completedBlock</span><span class="p">(</span><span class="n">image</span><span class="p">,</span> <span class="kp">nil</span><span class="p">,</span> <span class="n">cacheType</span><span class="p">,</span> <span class="no">YES</span><span class="p">,</span> <span class="n">url</span><span class="p">);</span>
           <span class="p">});</span>
       <span class="p">}</span>
       <span class="sr">//</span> <span class="err">如果没有图片或者被请求刷新，则开始下载操作</span>
       <span class="sr">//</span> <span class="err">通过缓存策略，从而确定下载策略</span>
       <span class="sr">//</span> <span class="err">下载策略下文会详细总结</span>
       <span class="no">SDWebImageDownloaderOptions</span> <span class="n">downloaderOptions</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
       <span class="sr">//</span> <span class="err">如果缓存策略为</span> <span class="no">SDWebImageLowPriority</span> <span class="err">活动减慢开始策略</span>
       <span class="sr">//</span> <span class="err">下载策略为</span> <span class="no">SDWebImageDownloaderLowPriority</span> <span class="err">默认模式</span>
       <span class="k">if</span> <span class="p">(</span><span class="n">options</span> <span class="o">&amp;</span> <span class="no">SDWebImageLowPriority</span><span class="p">)</span> <span class="n">downloaderOptions</span> <span class="o">|=</span> <span class="no">SDWebImageDownloaderLowPriority</span><span class="p">;</span>
       <span class="sr">//</span> <span class="err">如果缓存策略为</span> <span class="no">SDWebImageDownloaderLowPriority</span> <span class="err">渐进式策略</span>
       <span class="sr">//</span> <span class="err">下载策略为</span> <span class="no">SDWebImageDownloaderProgressiveDownload</span> <span class="err">渐进式下载策略</span>
       <span class="k">if</span> <span class="p">(</span><span class="n">options</span> <span class="o">&amp;</span> <span class="no">SDWebImageProgressiveDownload</span><span class="p">)</span> <span class="n">downloaderOptions</span> <span class="o">|=</span> <span class="no">SDWebImageDownloaderProgressiveDownload</span><span class="p">;</span>
       <span class="sr">//</span> <span class="err">如果缓存策略为</span> <span class="no">SDWebImageRefreshCached</span> <span class="err">刷新缓存策略</span>
       <span class="sr">//</span> <span class="err">下载策略为</span> <span class="no">SDWebImageDownloaderUseNSURLCache</span> <span class="err">不使用</span> <span class="no">Cache</span> <span class="err">方式</span>
       <span class="k">if</span> <span class="p">(</span><span class="n">options</span> <span class="o">&amp;</span> <span class="no">SDWebImageRefreshCached</span><span class="p">)</span> <span class="n">downloaderOptions</span> <span class="o">|=</span> <span class="no">SDWebImageDownloaderUseNSURLCache</span><span class="p">;</span>
       <span class="sr">//</span> <span class="err">如果缓存策略为</span> <span class="no">SDWebImageContinueInBackground</span> <span class="err">后台下载策略</span>
       <span class="sr">//</span> <span class="err">下载策略为</span> <span class="no">SDWebImageDownloaderContinueInBackground</span> <span class="err">后台加载</span><span class="p">(</span><span class="n">iOS</span> <span class="mi">4</span><span class="o">+</span><span class="p">)</span>
       <span class="k">if</span> <span class="p">(</span><span class="n">options</span> <span class="o">&amp;</span> <span class="no">SDWebImageContinueInBackground</span><span class="p">)</span> <span class="n">downloaderOptions</span> <span class="o">|=</span> <span class="no">SDWebImageDownloaderContinueInBackground</span><span class="p">;</span>
       <span class="sr">//</span> <span class="err">如果缓存策略为</span> <span class="no">SDWebImageHandleCookies</span> <span class="no">Cookie</span> <span class="err">策略</span>
       <span class="sr">//</span> <span class="err">下载策略为</span> <span class="no">SDWebImageDownloaderHandleCookies</span> <span class="err">使用</span> <span class="no">NSHTTPCookieStore</span> <span class="err">存储</span> <span class="n">cookie</span> <span class="err">策略</span>
       <span class="k">if</span> <span class="p">(</span><span class="n">options</span> <span class="o">&amp;</span> <span class="no">SDWebImageHandleCookies</span><span class="p">)</span> <span class="n">downloaderOptions</span> <span class="o">|=</span> <span class="no">SDWebImageDownloaderHandleCookies</span><span class="p">;</span>
       <span class="sr">//</span> <span class="err">如果缓存策略为</span> <span class="no">SDWebImageAllowInvalidSSLCertificates</span> <span class="err">允许无效的</span> <span class="no">SSL</span> <span class="err">验证策略</span>
       <span class="sr">//</span> <span class="err">下载策略为</span> <span class="no">SDWebImageDownloaderAllowInvalidSSLCertificates</span> <span class="err">允许不受信任的</span><span class="no">SSL</span><span class="err">证书</span><span class="p">(</span><span class="err">常测试用</span><span class="p">)</span>
       <span class="k">if</span> <span class="p">(</span><span class="n">options</span> <span class="o">&amp;</span> <span class="no">SDWebImageAllowInvalidSSLCertificates</span><span class="p">)</span> <span class="n">downloaderOptions</span> <span class="o">|=</span> <span class="no">SDWebImageDownloaderAllowInvalidSSLCertificates</span><span class="p">;</span>
       <span class="sr">//</span> <span class="err">如果缓存策略为</span> <span class="no">SDWebImageHighPriority</span> <span class="err">高级别的线程权限策略</span>
       <span class="sr">//</span> <span class="err">则下载策略为</span> <span class="no">SDWebImageDownloaderHighPriority</span> <span class="err">将图片下载放到高优先级队列中策略</span>
       <span class="k">if</span> <span class="p">(</span><span class="n">options</span> <span class="o">&amp;</span> <span class="no">SDWebImageHighPriority</span><span class="p">)</span> <span class="n">downloaderOptions</span> <span class="o">|=</span> <span class="no">SDWebImageDownloaderHighPriority</span><span class="p">;</span>
       <span class="sr">//</span> <span class="err">如果使用的是刷新缓存策略</span>
       <span class="k">if</span> <span class="p">(</span><span class="n">image</span> <span class="o">&amp;&amp;</span> <span class="n">options</span> <span class="o">&amp;</span> <span class="no">SDWebImageRefreshCached</span><span class="p">)</span> <span class="p">{</span>
           <span class="sr">//</span> <span class="err">强制解除渐进式下载策略</span>
           <span class="n">downloaderOptions</span> <span class="o">&amp;=</span> <span class="o">~</span><span class="no">SDWebImageDownloaderProgressiveDownload</span><span class="p">;</span>
           <span class="sr">//</span> <span class="err">忽略缓存结果，强制刷新</span>
           <span class="n">downloaderOptions</span> <span class="o">|=</span> <span class="no">SDWebImageDownloaderIgnoreCachedResponse</span><span class="p">;</span>
       <span class="p">}</span>
       <span class="sr">//</span> <span class="err">进行下载图片</span>
       <span class="nb">id</span> <span class="o">&lt;</span><span class="no">SDWebImageOperation</span><span class="o">&gt;</span> <span class="n">subOperation</span> <span class="o">=</span> <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">imageDownloader</span> <span class="ss">downloadImageWithURL</span><span class="p">:</span><span class="n">url</span> <span class="ss">options</span><span class="p">:</span><span class="n">downloaderOptions</span> <span class="ss">progress</span><span class="p">:</span><span class="n">progressBlock</span> <span class="ss">completed</span><span class="p">:</span><span class="o">^</span><span class="p">(</span><span class="no">UIImage</span> <span class="o">*</span><span class="n">downloadedImage</span><span class="p">,</span> <span class="no">NSData</span> <span class="o">*</span><span class="n">data</span><span class="p">,</span> <span class="no">NSError</span> <span class="o">*</span><span class="n">error</span><span class="p">,</span> <span class="no">BOOL</span> <span class="n">finished</span><span class="p">)</span> <span class="p">{</span>
           <span class="n">__strong</span> <span class="n">__typeof</span><span class="p">(</span><span class="n">weakOperation</span><span class="p">)</span> <span class="n">strongOperation</span> <span class="o">=</span> <span class="n">weakOperation</span><span class="p">;</span>
           <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strongOperation</span> <span class="o">||</span> <span class="n">strongOperation</span><span class="o">.</span><span class="n">isCancelled</span><span class="p">)</span> <span class="p">{</span>
               <span class="sr">//</span> <span class="err">如果</span> <span class="n">operation</span> <span class="err">被取消，则不进行任何操作</span>
               <span class="sr">//</span> <span class="err">如果我们调用</span> <span class="n">completedBlock</span><span class="err">，可能会使得</span> <span class="n">completedBlock</span> <span class="err">和此</span> <span class="n">block</span> <span class="err">产生选择竞争，如果对其进行二次访问，则会重写</span>
           <span class="p">}</span>
           <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">error</span><span class="p">)</span> <span class="p">{</span>
               <span class="sr">//</span> <span class="err">错误情况下，直接对</span> <span class="n">completedBlock</span> <span class="err">进行同步调用</span>
               <span class="n">dispatch_main_sync_safe</span><span class="p">(</span><span class="o">^</span><span class="p">{</span>
                   <span class="k">if</span> <span class="p">(</span><span class="n">strongOperation</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">strongOperation</span><span class="o">.</span><span class="n">isCancelled</span><span class="p">)</span> <span class="p">{</span>
                       <span class="n">completedBlock</span><span class="p">(</span><span class="kp">nil</span><span class="p">,</span> <span class="n">error</span><span class="p">,</span> <span class="no">SDImageCacheTypeNone</span><span class="p">,</span> <span class="n">finished</span><span class="p">,</span> <span class="n">url</span><span class="p">);</span>
                   <span class="p">}</span>
               <span class="p">});</span>
               <span class="sr">//</span> <span class="err">错误来源不是非</span> <span class="n">url</span> <span class="err">无效造成的，则将</span> <span class="n">url</span> <span class="err">归类为无效</span> <span class="n">url</span> <span class="err">并缓存起来</span>
               <span class="k">if</span> <span class="p">(</span>   <span class="n">error</span><span class="o">.</span><span class="n">code</span> <span class="o">!=</span> <span class="no">NSURLErrorNotConnectedToInternet</span>
                   <span class="o">&amp;&amp;</span> <span class="n">error</span><span class="o">.</span><span class="n">code</span> <span class="o">!=</span> <span class="no">NSURLErrorCancelled</span>
                   <span class="o">&amp;&amp;</span> <span class="n">error</span><span class="o">.</span><span class="n">code</span> <span class="o">!=</span> <span class="no">NSURLErrorTimedOut</span>
                   <span class="o">&amp;&amp;</span> <span class="n">error</span><span class="o">.</span><span class="n">code</span> <span class="o">!=</span> <span class="no">NSURLErrorInternationalRoamingOff</span>
                   <span class="o">&amp;&amp;</span> <span class="n">error</span><span class="o">.</span><span class="n">code</span> <span class="o">!=</span> <span class="no">NSURLErrorDataNotAllowed</span>
                   <span class="o">&amp;&amp;</span> <span class="n">error</span><span class="o">.</span><span class="n">code</span> <span class="o">!=</span> <span class="no">NSURLErrorCannotFindHost</span>
                   <span class="o">&amp;&amp;</span> <span class="n">error</span><span class="o">.</span><span class="n">code</span> <span class="o">!=</span> <span class="no">NSURLErrorCannotConnectToHost</span><span class="p">)</span> <span class="p">{</span>
                   <span class="vi">@synchronized</span> <span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">failedURLs</span><span class="p">)</span> <span class="p">{</span>
                       <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">failedURLs</span> <span class="ss">addObject</span><span class="p">:</span><span class="n">url</span><span class="o">]</span><span class="p">;</span>
                   <span class="p">}</span>
               <span class="p">}</span>
           <span class="p">}</span>
           <span class="k">else</span> <span class="p">{</span>
               <span class="sr">//</span> <span class="err">如果缓存策略选用了刷新缓存的话，从失效</span> <span class="n">url</span> <span class="err">表中移除</span>
               <span class="k">if</span> <span class="p">((</span><span class="n">options</span> <span class="o">&amp;</span> <span class="no">SDWebImageRetryFailed</span><span class="p">))</span> <span class="p">{</span>
                   <span class="vi">@synchronized</span> <span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">failedURLs</span><span class="p">)</span> <span class="p">{</span>
                       <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">failedURLs</span> <span class="ss">removeObject</span><span class="p">:</span><span class="n">url</span><span class="o">]</span><span class="p">;</span>
                   <span class="p">}</span>
               <span class="p">}</span>
               <span class="sr">//</span> <span class="err">查看是否使用了</span> <span class="err">仅内存存储</span> <span class="err">策略</span>
               <span class="no">BOOL</span> <span class="n">cacheOnDisk</span> <span class="o">=</span> <span class="o">!</span><span class="p">(</span><span class="n">options</span> <span class="o">&amp;</span> <span class="no">SDWebImageCacheMemoryOnly</span><span class="p">);</span>
               <span class="k">if</span> <span class="p">(</span><span class="n">options</span> <span class="o">&amp;</span> <span class="no">SDWebImageRefreshCached</span> <span class="o">&amp;&amp;</span> <span class="n">image</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">downloadedImage</span><span class="p">)</span> <span class="p">{</span>
                   <span class="sr">//</span>  <span class="no">NSURLCache</span> <span class="err">刷新缓存，不调用</span> <span class="n">completeBlock</span>
               <span class="p">}</span>
               <span class="sr">//</span> <span class="err">如果下载成功，并且成功捕获代理方法，则需要对图片进行转换</span>
               <span class="sr">//</span> <span class="err">多用于</span> <span class="err">图片组</span> <span class="err">策略，需要不断获取图片</span>
               <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">downloadedImage</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="o">!</span><span class="n">downloadedImage</span><span class="o">.</span><span class="n">images</span> <span class="o">||</span> <span class="p">(</span><span class="n">options</span> <span class="o">&amp;</span> <span class="no">SDWebImageTransformAnimatedImage</span><span class="p">))</span> <span class="o">&amp;&amp;</span> <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">delegate</span> <span class="ss">respondsToSelector</span><span class="p">:</span><span class="vi">@selector</span><span class="p">(</span><span class="ss">imageManager</span><span class="p">:</span><span class="ss">transformDownloadedImage</span><span class="p">:</span><span class="ss">withURL</span><span class="p">:)</span><span class="o">]</span><span class="p">)</span> <span class="p">{</span>
                   <span class="sr">//</span> <span class="err">全局队列中，异步操作</span>
                   <span class="sr">//</span> <span class="err">询问代理是否要在</span> <span class="n">image</span> <span class="err">存储到缓存之前做最后的图片编号操作（缩放、剪切、圆角等）</span>
                   <span class="n">dispatch_async</span><span class="p">(</span><span class="n">dispatch_get_global_queue</span><span class="p">(</span><span class="no">DISPATCH_QUEUE_PRIORITY_HIGH</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="o">^</span><span class="p">{</span>
                       <span class="sr">//</span> <span class="err">根据代理方法获取转换后的图片</span>
                       <span class="no">UIImage</span> <span class="o">*</span><span class="n">transformedImage</span> <span class="o">=</span> <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">delegate</span> <span class="ss">imageManager</span><span class="p">:</span><span class="nb">self</span> <span class="ss">transformDownloadedImage</span><span class="p">:</span><span class="n">downloadedImage</span> <span class="ss">withURL</span><span class="p">:</span><span class="n">url</span><span class="o">]</span><span class="p">;</span>
                       <span class="sr">//</span> <span class="err">如果图片存在</span> <span class="err">并且</span> <span class="err">转换已经完成</span>
                       <span class="k">if</span> <span class="p">(</span><span class="n">transformedImage</span> <span class="o">&amp;&amp;</span> <span class="n">finished</span><span class="p">)</span> <span class="p">{</span>
                           <span class="sr">//</span> <span class="err">将图片存入缓存</span>
                           <span class="no">BOOL</span> <span class="n">imageWasTransformed</span> <span class="o">=</span> <span class="o">![</span><span class="n">transformedImage</span> <span class="ss">isEqual</span><span class="p">:</span><span class="n">downloadedImage</span><span class="o">]</span><span class="p">;</span>
                           <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">imageCache</span> <span class="ss">storeImage</span><span class="p">:</span><span class="n">transformedImage</span> <span class="ss">recalculateFromImage</span><span class="p">:</span><span class="n">imageWasTransformed</span> <span class="ss">imageData</span><span class="p">:(</span><span class="n">imageWasTransformed</span> <span class="p">?</span> <span class="kp">nil</span> <span class="p">:</span> <span class="n">data</span><span class="p">)</span> <span class="ss">forKey</span><span class="p">:</span><span class="n">key</span> <span class="ss">toDisk</span><span class="p">:</span><span class="n">cacheOnDisk</span><span class="o">]</span><span class="p">;</span>
                       <span class="p">}</span>
                       <span class="sr">//</span> <span class="err">同步调用</span> <span class="n">completedBlock</span>
                       <span class="n">dispatch_main_sync_safe</span><span class="p">(</span><span class="o">^</span><span class="p">{</span>
                           <span class="k">if</span> <span class="p">(</span><span class="n">strongOperation</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">strongOperation</span><span class="o">.</span><span class="n">isCancelled</span><span class="p">)</span> <span class="p">{</span>
                               <span class="n">completedBlock</span><span class="p">(</span><span class="n">transformedImage</span><span class="p">,</span> <span class="kp">nil</span><span class="p">,</span> <span class="no">SDImageCacheTypeNone</span><span class="p">,</span> <span class="n">finished</span><span class="p">,</span> <span class="n">url</span><span class="p">);</span>
                           <span class="p">}</span>
                       <span class="p">});</span>
                   <span class="p">});</span>
               <span class="p">}</span>
               <span class="k">else</span> <span class="p">{</span>
                   <span class="sr">//</span> <span class="err">没有实现代理方法，进行缓存操作</span>
                   <span class="k">if</span> <span class="p">(</span><span class="n">downloadedImage</span> <span class="o">&amp;&amp;</span> <span class="n">finished</span><span class="p">)</span> <span class="p">{</span>
                       <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">imageCache</span> <span class="ss">storeImage</span><span class="p">:</span><span class="n">downloadedImage</span> <span class="ss">recalculateFromImage</span><span class="p">:</span><span class="no">NO</span> <span class="ss">imageData</span><span class="p">:</span><span class="n">data</span> <span class="ss">forKey</span><span class="p">:</span><span class="n">key</span> <span class="ss">toDisk</span><span class="p">:</span><span class="n">cacheOnDisk</span><span class="o">]</span><span class="p">;</span>
                   <span class="p">}</span>
                   <span class="sr">//</span> <span class="n">completedBlock</span> <span class="err">回调</span>
                   <span class="n">dispatch_main_sync_safe</span><span class="p">(</span><span class="o">^</span><span class="p">{</span>
                       <span class="k">if</span> <span class="p">(</span><span class="n">strongOperation</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">strongOperation</span><span class="o">.</span><span class="n">isCancelled</span><span class="p">)</span> <span class="p">{</span>
                           <span class="n">completedBlock</span><span class="p">(</span><span class="n">downloadedImage</span><span class="p">,</span> <span class="kp">nil</span><span class="p">,</span> <span class="no">SDImageCacheTypeNone</span><span class="p">,</span> <span class="n">finished</span><span class="p">,</span> <span class="n">url</span><span class="p">);</span>
                       <span class="p">}</span>
                   <span class="p">});</span>
               <span class="p">}</span>
           <span class="p">}</span>
           <span class="sr">//</span> <span class="err">确认结束，从</span> <span class="n">operation</span> <span class="n">list</span> <span class="err">删除</span>
           <span class="k">if</span> <span class="p">(</span><span class="n">finished</span><span class="p">)</span> <span class="p">{</span>
               <span class="vi">@synchronized</span> <span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">runningOperations</span><span class="p">)</span> <span class="p">{</span>
                   <span class="k">if</span> <span class="p">(</span><span class="n">strongOperation</span><span class="p">)</span> <span class="p">{</span>
                       <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">runningOperations</span> <span class="ss">removeObject</span><span class="p">:</span><span class="n">strongOperation</span><span class="o">]</span><span class="p">;</span>
                   <span class="p">}</span>
               <span class="p">}</span>
           <span class="p">}</span>
       <span class="p">}</span><span class="o">]</span><span class="p">;</span>
       <span class="n">operation</span><span class="o">.</span><span class="n">cancelBlock</span> <span class="o">=</span> <span class="o">^</span><span class="p">{</span>
           <span class="sr">//</span> <span class="err">调用</span> <span class="n">cancel</span> <span class="err">方法，这个是</span> <span class="n">download</span> <span class="err">的</span> <span class="n">operation</span><span class="err">，具体实现先后文</span>
           <span class="o">[</span><span class="n">subOperation</span> <span class="n">cancel</span><span class="o">]</span><span class="p">;</span>
           <span class="sr">//</span> <span class="err">对</span> <span class="n">runningOperations</span> <span class="err">加互斥锁</span>
           <span class="vi">@synchronized</span> <span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">runningOperations</span><span class="p">)</span> <span class="p">{</span>
               <span class="sr">//</span> <span class="err">转为</span> <span class="n">__strong</span> <span class="err">保证生命周期</span>
               <span class="n">__strong</span> <span class="n">__typeof</span><span class="p">(</span><span class="n">weakOperation</span><span class="p">)</span> <span class="n">strongOperation</span> <span class="o">=</span> <span class="n">weakOperation</span><span class="p">;</span>
               <span class="k">if</span> <span class="p">(</span><span class="n">strongOperation</span><span class="p">)</span> <span class="p">{</span>
                   <span class="sr">//</span> <span class="err">从</span> <span class="n">opertaion</span> <span class="n">list</span> <span class="err">中删除</span>
                   <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">runningOperations</span> <span class="ss">removeObject</span><span class="p">:</span><span class="n">strongOperation</span><span class="o">]</span><span class="p">;</span>
               <span class="p">}</span>
           <span class="p">}</span>
       <span class="p">};</span>
   <span class="p">}</span>
   <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">image</span><span class="p">)</span> <span class="p">{</span>
       <span class="sr">//</span> <span class="err">缓存查询命中图片</span>
       <span class="sr">//</span> <span class="err">线程没用被取消</span>
       <span class="n">dispatch_main_sync_safe</span><span class="p">(</span><span class="o">^</span><span class="p">{</span>
           <span class="n">__strong</span> <span class="n">__typeof</span><span class="p">(</span><span class="n">weakOperation</span><span class="p">)</span> <span class="n">strongOperation</span> <span class="o">=</span> <span class="n">weakOperation</span><span class="p">;</span>
           <span class="k">if</span> <span class="p">(</span><span class="n">strongOperation</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">strongOperation</span><span class="o">.</span><span class="n">isCancelled</span><span class="p">)</span> <span class="p">{</span>
               <span class="n">completedBlock</span><span class="p">(</span><span class="n">image</span><span class="p">,</span> <span class="kp">nil</span><span class="p">,</span> <span class="n">cacheType</span><span class="p">,</span> <span class="no">YES</span><span class="p">,</span> <span class="n">url</span><span class="p">);</span>
           <span class="p">}</span>
       <span class="p">});</span>
       <span class="vi">@synchronized</span> <span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">runningOperations</span><span class="p">)</span> <span class="p">{</span>
           <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">runningOperations</span> <span class="ss">removeObject</span><span class="p">:</span><span class="n">operation</span><span class="o">]</span><span class="p">;</span>
       <span class="p">}</span>
   <span class="p">}</span>
   <span class="k">else</span> <span class="p">{</span>
       <span class="sr">//</span> <span class="err">图像不在缓存中且下载失效</span>
       <span class="sr">//</span> <span class="err">直接调用</span> <span class="n">completedBlock</span>
       <span class="n">dispatch_main_sync_safe</span><span class="p">(</span><span class="o">^</span><span class="p">{</span>
           <span class="n">__strong</span> <span class="n">__typeof</span><span class="p">(</span><span class="n">weakOperation</span><span class="p">)</span> <span class="n">strongOperation</span> <span class="o">=</span> <span class="n">weakOperation</span><span class="p">;</span>
           <span class="k">if</span> <span class="p">(</span><span class="n">strongOperation</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">weakOperation</span><span class="o">.</span><span class="n">isCancelled</span><span class="p">)</span> <span class="p">{</span>
               <span class="n">completedBlock</span><span class="p">(</span><span class="kp">nil</span><span class="p">,</span> <span class="kp">nil</span><span class="p">,</span> <span class="no">SDImageCacheTypeNone</span><span class="p">,</span> <span class="no">YES</span><span class="p">,</span> <span class="n">url</span><span class="p">);</span>
           <span class="p">}</span>
       <span class="p">});</span>
       <span class="vi">@synchronized</span> <span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">runningOperations</span><span class="p">)</span> <span class="p">{</span>
           <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">runningOperations</span> <span class="ss">removeObject</span><span class="p">:</span><span class="n">operation</span><span class="o">]</span><span class="p">;</span>
       <span class="p">}</span>
   <span class="p">}</span>
<span class="p">}</span><span class="o">]</span><span class="p">;</span>
<span class="k">return</span> <span class="n">operation</span><span class="p">;</span></code></pre></div>
<p>在分析完冗长的代码之后，来整体把握一下 <code>SDWebImageManager</code> 做了哪些事情：</p>
<h4 id="cache--downloader-">1. 持有 Cache 和 Downloader 协同处理</h4>
<p>在上述代码中，经常可以看到 Cache 和 Downloader 的身影。</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="ss">queryDiskCacheForKey</span><span class="p">:</span></code></pre></div>
<p>Cache 是图片获取的最快方式，所以在任何的 sd_set 方法上，无需立即开启 downloader 进行下载操作，而是先根据 key 从 cache 中查询。这里的 key 就是图片对应的 url 。</p>
<p>对于 Cache 的原理，在后续文中会做详细描述。</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="ss">downloadImageWithURL</span><span class="p">:</span></code></pre></div>
<p>在 Cache 查询中无法命中后，需要启动 Downloader 进行图片下载。这个方法也是 Downloader 对外暴露的接口方法，我们可以主动调用该方法进行图片下载工作。</p>
<p>对于 Downloader 的原理，在后续文中会做详细描述。</p>
<h4 id="section-1">2. 缓存策略与下载策略的映射</h4>
<p>在上文中列举出了所有的缓存策略，其实在 Downloader 中有定义下载策略的枚举。具体代码后续文中会写出，这里列出一个映射图：</p>
<p><img src="http://7xwh85.com1.z0.glb.clouddn.com/options.png" alt="options" /></p>
<p>下载策略是根据缓存策略而决定的，而当需要重新刷新缓存策略生效的时候，会启动策略调整操作。这样保证了各个策略的协调不冲突。</p>
<h4 id="nsmutableset--url-">3. 运用 NSMutableSet 进行 url 合法性管理</h4>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">isFailedUrl</span> <span class="o">=</span> <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">failedURLs</span> <span class="ss">containsObject</span><span class="p">:</span><span class="n">url</span><span class="o">]</span><span class="p">;</span></code></pre></div>
<p>源码中有这么一行代码，用来判断 url 是否合法。</p>
<p>在容器类使用上，SD 使用了 NSMutableSet ，即实现了去重，又增加了查询速度。对于 NSSet 的实现，笔者还没有具体的了解，而在 Google 上的大多数资料说 NSSet 是通过 hash 来实现的。这也难怪查询元素的时候的低时间开销。</p>
<p>而对于每个图片的 url，SD 为了优化，将 image 对应的 url 通过关联对象添加至各个 UIImageView（UIButton），实现了更加高效的访问。所以说，在 url 的管理上，SD 的做法非常值得借鉴。</p>
<h4 id="section-2">4. 时刻注意因异步带来的选择竞争问题</h4>
<p>在整个流程中，会经常看到增加互斥量的操作。因为 SD 在不断的开启子线程进行下载操作，所以在管理 operation 或者 url 的时候，一定要考虑到这些问题。</p>
<h2 id="manager-">Manager 的流程一览</h2>
<p>来总结一下 <code>SDWebImageManager</code> 在图片获取的过程中所经历的所有流程：</p>
<p><img src="http://7xwh85.com1.z0.glb.clouddn.com/manager.png" alt="manage" /></p>
<p>其中，很多合法性判断过程中，如果不满足继续下载的合法条件，则会直接调用 completedBlock 回调，并返回未下载成功的 operation 操作对象。</p>
<h2 id="section-3">写在最后</h2>
<p>至此，我们完成了对 <code>SDWebImageManager</code> 的分析。该系列下一篇文将对 downloader class 进行详细剖析。</p>
  <footer class="post-footer">
    <section class="author">
      <h4>Desgard_Duan</h4>
      <p>I write many code to write less code.💻</p>
    </section>
<aside class="share">
  <h4>Share this.</h4>
  <a href="http://twitter.com/share?text=《SDWebImage Source Probe: Manager》 -- Guardia · 瓜地&amp;url=https://www.desgard.com/SDWebImage2/"
  onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
    <i class="fa fa-twitter-square"></i>
  </a>
  <a href="http://v.t.sina.com.cn/share/share.php?url=https://www.desgard.com/SDWebImage2/&amp;title=《SDWebImage Source Probe: Manager》 —— Guardia · 瓜地" onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
    <i class="fa fa-weibo"></i>
  </a>
</aside>
      <hr>
<aside id="comments" class="disqus">
  <div id="container"></div>
  <!-- <link rel="stylesheet" href="/assets/css/gitment.css">
  <script src="https://imsun.github.io/gitment/dist/gitment.browser.js"></script>
  <script>
  var gitment = new Gitment({
    id: "https://www.desgard.com/SDWebImage2/", 
    owner: 'Desgard',
    repo: 'desgard.github.com',
    oauth: {
      client_id: 'e2612df42f3f2a83e71c',
      client_secret: 'b53e85b314bb24a6d06773e48bbb62a4de3b8b3a',
    },
  })
  gitment.render('container')
  </script> -->
<link rel="stylesheet" href="/assets/css/gitalk.css">
<script src="https://unpkg.com/gitalk/dist/gitalk.min.js"></script>
<div id='gitalk-container'></div>
<script>
    const gitalk = new Gitalk({
        id: "https://www.desgard.com/SDWebImage2/", 
        clientID: 'e2612df42f3f2a83e71c',
        clientSecret: 'b53e85b314bb24a6d06773e48bbb62a4de3b8b3a',
        repo: 'desgard.github.com',
        owner: 'Desgard',
        admin: ['Desgard'],
        // facebook-like distraction free mode
        distractionFreeMode: false
    })
    gitalk.render('gitalk-container')
</script>
</aside>
  </footer>
</article>
    </main>
<footer class="footer">
  <div class="container">
    <ul class="icons">
      <li>
        <a href="https://github.com/desgard" class="icon-github" target="_blank">
          <i class="fa fa-github"></i>
        </a>
      </li>
      <li>
        <a href="https://www.facebook.com/desgard.duan" class="icon-facebook" target="_blank">
          <i class="fa fa-facebook"></i>
        </a>
      </li>
      <li>
        <a href="https://twitter.com/Desgard_Duan" class="icon-twitter" target="_blank">
          <i class="fa fa-twitter"></i>
        </a>
      </li>
      <li>
        <a href="https://stackoverflow.com/users/6119149/desgard-duan" class="icon-github" target="_blank">
          <i class="fa fa-stack-overflow"></i>
        </a>
      </li>
      <li>
        <a href="https://weibo.com/desgard" class="icon-instagram" target="_blank">
          <i class="fa fa-weibo"></i>
        </a>
      </li>
    </ul>
    <p>
      <q>I write many code to write less code.💻</q>
      <small>– Gua</small>
    </p>
    <small class="clearfix">
      Powered by <a href="http://jekyllrb.com" target="_blank">Jekyll</a> • <a href="https://github.com/desgard" target="_blank">Open source <i class="fa fa-heart"></i></a>
    </small>
  </div>
</footer>
<a class="scroll-up fa fa-chevron-up bounce" href="#" data-position="0"></a>
<div id="modalSearch" class="modal">
  <div class="modal__overlay"></div>
  <div class="modal__content">
    <a href="#!" class="modal-close" data-modal-close>&times;</a>
    <div class="search-container">
      <input type="text" id="search-input" placeholder="Search articles">
      <ul id="results-container"></ul>
    </div>
  </div>
</div>
    <script src="/assets/main-52d417e8a6ff9f5b168386d37c96338a.js"></script>
  </body>
  <script>
    var link = "" ;
    var os = function() {  
      var ua = navigator.userAgent,  
      isWindowsPhone = /(?:Windows Phone)/.test(ua),  
      isSymbian = /(?:SymbianOS)/.test(ua) || isWindowsPhone,   
      isAndroid = /(?:Android)/.test(ua),   
      isFireFox = /(?:Firefox)/.test(ua),   
      isChrome = /(?:Chrome|CriOS)/.test(ua),  
      isTablet = /(?:iPad|PlayBook)/.test(ua) || (isAndroid && !/(?:Mobile)/.test(ua)) || (isFireFox && /(?:Tablet)/.test(ua)),  
      isPhone = /(?:iPhone)/.test(ua) && !isTablet,  
      isPc = !isPhone && !isAndroid && !isSymbian;  
      return {  
        isTablet: isTablet,  
        isPhone: isPhone,  
        isAndroid : isAndroid,  
        isPc : isPc  
      };  
    }();  
    if (link.length > 0) {
      if (os.isAndroid || os.isPhone) {
        location.replace(link);
      }
    }
  </script>
</html>